#ifndef exports_H
#define exports_H

#include <iostream>  //must be deleted
#include <fstream>
#include <vector>

#include "mesh.h"

namespace ABAN
{
    class VTKExport
    {
        private:
        struct CellConnection
        {
            SeqType nodeIdxs[nodesPerCell];
            void operator=(CellConnection &otherCell)
            {
                for (SeqType i=dirXP; i<nodesPerCell;i++)
                    nodeIdxs[i]=otherCell.nodeIdxs[i];
            }
        };

        struct NodeConnectionData
        {
            CellIterator cell;
            SeqType idxInCell;

            NodeConnectionData(CellIterator aCell,SeqType aIdx)
                :cell(aCell),idxInCell(aIdx){}
        };

        typedef std::vector<NodeConnectionData> NodeConnectionDataVector;
        typedef NodeConnectionDataVector::iterator NodeConnectionDataVectorIterator;

        struct NodeConnection
        {
            NodeConnectionDataVector cellConnection;
            Vector coord;
            FloatType sigmaWeight;

            FloatType p;
            Vector vel;

            NodeConnection():coord(0.0,0.0,0.0),sigmaWeight(0),p(0),vel(0.0,0.0,0.0){}
            FloatType weight(CellIterator aCell, Vector nodeCoord)
            {
                return 1.0/aCell->volume;
            }
            void addCell(CellContainer *aMesh, CellIterator aCell, SeqType idxInCell)
            {
                FloatType cellWeight=weight(aCell,coord);
                sigmaWeight+=cellWeight;

                SeqType cellNo=aCell->no;
                Vector weightedVel((*aMesh->vels[dirXP])[cellNo],
                                   (*aMesh->vels[dirYP])[cellNo],
                                   (*aMesh->vels[dirZP])[cellNo]);
                weightedVel*=cellWeight;
                vel+=weightedVel;
                p+=cellWeight*(*aMesh->p)[cellNo];

                cellConnection.push_back(NodeConnectionData(aCell,idxInCell));
            }
        };

        typedef std::vector<CellConnection> ExporterCellContainer;
        typedef std::vector<NodeConnection> ExporterNodeContainer;

        ExporterCellContainer *cells;
        ExporterNodeContainer *nodes;

        //find a node of a cell on corresponding faces of neighbor cells and returns its index in nodes (-1 if not found)
        SeqType findNodeOnNeighbors(CellContainer *aMesh,CellIterator thisCell, SeqType thisNode);
        //gathers connectivity information for each cell and those nodes belong to those cells
        void generatePrimaryConnectivities(CellContainer *aMesh);
        //gathers connectivity information for each node on cells are NOT connected to but have effect on them
        void generateSecondaryConnectivities(CellContainer *aMesh);
        bool hasSecondaryEffect(CellIterator mainCell, CellIterator neighbCell, SeqType face);

        void checkCellsOfNode(CellContainer *aMesh, NodeConnection &node);
        void uncheckCellsOfNode(CellContainer *aMesh, NodeConnection &node);

        void writeToStream(std::ofstream &out/*,DoubleDatumVector* dummyCellVals*/);//dummy cell vals must be deleted

        public:
        VTKExport(SeqType nCell)
        {
            cells=new ExporterCellContainer(nCell);
            nodes=new ExporterNodeContainer;
        }
        ~VTKExport(){delete cells;delete nodes;}
        static void run(CellContainer *aMesh, std::string fileName/*,DoubleDatumVector* dummyCellVals*/);//dummy cell vals must be deleted
    };
    //-------------------------------------------------------
    SeqType VTKExport::findNodeOnNeighbors(CellContainer *aMesh,CellIterator thisCell, SeqType thisNode)
    {
        bool found = false;
        SeqType nodeIdx;
        Vector currNode = thisCell->getNode(thisNode);

        //finding this node in neighbors
        for (SeqType faceCounter=dirXP; (faceCounter<noOfAxis)&&(!found); faceCounter++)
        {
            //the face that this nodes belongs to
            SeqType face = facesOfNodes[thisNode][faceCounter];

            for (CellNeighborhoodSetIterator neighb=thisCell->neighbors[face]->begin();
                 (neighb!=thisCell->neighbors[face]->end())&&(!found);
                 neighb++)
                //if there is a possibility of node existance in previous trys
                if ((*neighb)->checked==aMesh->currCheckedVal)
                    for (SeqType neighbNodeCounter=0;
                         (neighbNodeCounter<nodesPerFace)&&(!found);
                         neighbNodeCounter++)
                    {
                        nodeIdx=(*cells)[(*neighb)->no].
                            nodeIdxs[nodeNumbersInFaces[oppositeSide[face]][neighbNodeCounter]];
                        found=((*nodes)[nodeIdx].coord==currNode);
                    }
        }//of face for

        return (found?nodeIdx:-1);
    }
    //-------------------------------------------------------
    void VTKExport::generatePrimaryConnectivities(CellContainer *aMesh)
    {
        SeqType lastNodeIdx = -1;
        aMesh->currCheckedVal = !aMesh->currCheckedVal;
        for (CellIterator cell=aMesh->begin(); cell!=aMesh->end(); cell++)
        {
            //recognising each node of this cell
            for (SeqType node=0; node<nodesPerCell; node++)
            {
                SeqType nodeIdx = findNodeOnNeighbors(aMesh,cell,node);
                Vector currNodeCoord = cell->getNode(node);

                //add node (new or found) to cell
                if (nodeIdx!=-1) (*cells)[cell->no].nodeIdxs[node]=nodeIdx;
                else
                {
                    NodeConnection addingNode;
                    addingNode.coord=currNodeCoord;
                    nodes->push_back(addingNode);
                    nodeIdx=(*cells)[cell->no].nodeIdxs[node]=++lastNodeIdx;
                }

                (*nodes)[nodeIdx].addCell(aMesh,cell,node);

            }//of node for
            cell->checked=!cell->checked;
        }//of cell for
    }
    //-------------------------------------------------------
    void VTKExport::checkCellsOfNode(CellContainer *aMesh, NodeConnection &node)
    {
        for(NodeConnectionDataVectorIterator iter=node.cellConnection.begin();
            iter!=node.cellConnection.end();
            iter++)
                 iter->cell->checked=aMesh->currCheckedVal;
    }
    //-------------------------------------------------------
    void VTKExport::uncheckCellsOfNode(CellContainer *aMesh, NodeConnection &node)
    {
        if (node.cellConnection.size()>8) std::cout<<"extera connection"<<endl;
        for(NodeConnectionDataVectorIterator iter=node.cellConnection.begin();
            iter!=node.cellConnection.end();
            iter++)
            {
                iter->cell->checked=!aMesh->currCheckedVal;
                 if (node.cellConnection.size()>8) std::cout<<iter->cell->no<<std::endl;
             }
    }
    //-------------------------------------------------------
    bool VTKExport::hasSecondaryEffect(CellIterator mainCell, CellIterator neighbCell, SeqType face)
    {
       bool amMinorInOneDirection=false, hasEffect=true;

        SeqType absoluteFace=(face<noOfAxis?face:oppositeSide[face]);
        for (SeqType dir=dirXP; dir<noOfAxis; dir++)
            if (dir!=absoluteFace)
            {
                FCRelation relation=
                    mainCell->fractCoord[dir].relation(neighbCell->fractCoord[dir]);

                hasEffect=hasEffect&&((relation==fcrEqual)||(relation==fcrMinor));
                amMinorInOneDirection=
                    amMinorInOneDirection || (relation==fcrEqual)||(relation==fcrMinor);
            }

        if (hasEffect)
            return hasEffect;
        else
            if (!amMinorInOneDirection) return hasEffect;


        /*if amMinorInOneDirection but not addThisNeighbor*/
        for (SeqType dir=dirXP; dir<noOfAxis; dir++)
            if (dir!=absoluteFace)
            {
                FCRelation relation=
                    mainCell->fractCoord[dir].relation(neighbCell->fractCoord[dir]);

                if (relation==fcrMajor)
                {
                    FractionalCoordinationComponent otherFCCoord = neighbCell->fractCoord[dir];
                    while (otherFCCoord.denominator!=mainCell->fractCoord[dir].denominator) otherFCCoord.collapse();
                    hasEffect=(mainCell->fractCoord[dir].relation(otherFCCoord)==fcrEqual);
                }

                hasEffect=(relation==fcrEqual)||(relation==fcrMinor);
            }


        return hasEffect;
    }
    //-------------------------------------------------------
    void VTKExport::generateSecondaryConnectivities(CellContainer *aMesh)
    {
        aMesh->currCheckedVal = !aMesh->currCheckedVal;

        for (SeqType totalNodeCounter=0; totalNodeCounter<nodes->size(); totalNodeCounter++)
        {
            checkCellsOfNode(aMesh, (*nodes)[totalNodeCounter]);

            SeqType nOwnerCells = (*nodes)[totalNodeCounter].cellConnection.size();//to prevent future adding cells
            for (SeqType checkingCellIdx=0; checkingCellIdx<nOwnerCells; checkingCellIdx++)
            {
                CellIterator checkingCell=(*nodes)[totalNodeCounter].cellConnection[checkingCellIdx].cell;
                for (SeqType faceCounter=dirXP; faceCounter<noOfAxis; faceCounter++)
                {
                    //the face that this nodes belongs to
                    SeqType face =
                        facesOfNodes[(*nodes)[totalNodeCounter].cellConnection[checkingCellIdx].idxInCell][faceCounter];

                    for (CellNeighborhoodSetIterator neighb=checkingCell->neighbors[face]->begin();
                         neighb!=checkingCell->neighbors[face]->end();
                         neighb++)
                        //if the node is NOT already encounted
                        if ((*neighb)->checked!=aMesh->currCheckedVal)
                            if (hasSecondaryEffect(checkingCell,(*neighb),face))
                            {
                                (*neighb)->checked=aMesh->currCheckedVal;
                                (*nodes)[totalNodeCounter].addCell(aMesh,*neighb,-1/*not a node of this cell*/);
                            }
                }
            }

            uncheckCellsOfNode(aMesh, (*nodes)[totalNodeCounter]);
        }

        aMesh->currCheckedVal = !aMesh->currCheckedVal;
    }
    //-------------------------------------------------------
    void VTKExport::writeToStream(std::ofstream &out/*,DoubleDatumVector* dummyCellVals*/)
    {
        const SeqType nodeSequence_VTK_HEXAHEDRON[nodesPerCell]={0,1,3,2,4,5,7,6};

        out<<"# vtk DataFile Version 2.0"<<std::endl;
        out<<"ABAN Output file"<<std::endl;
        out<<"ASCII"<<std::endl;
        out<<"DATASET UNSTRUCTURED_GRID"<<std::endl;

        out<<"POINTS "<<nodes->size()<<" float"<<std::endl;
        for (ExporterNodeContainer::iterator node=nodes->begin();
             node!=nodes->end();
             node++)
            {
                for (SeqType elem=dirXP; elem<noOfAxis; elem++)
                   out<<node->coord[elem]<<" ";
                out<<std::endl;
            }

        out<<"CELLS "<<cells->size()<<" "<<9*cells->size()<<std::endl;
        for (ExporterCellContainer::iterator cell=cells->begin();
             cell!=cells->end();
             cell++)
            {
                out<<nodesPerCell<<" ";
                for (SeqType node=0; node<nodesPerCell; node++)
                    out<<cell->nodeIdxs[nodeSequence_VTK_HEXAHEDRON[node]]<<" ";
                out<<std::endl;
            }

        out<<"CELL_TYPES "<<cells->size()<<std::endl;
        for (ExporterCellContainer::iterator cell=cells->begin();
             cell!=cells->end();
             cell++)
            out<<"12"<<std::endl;

        out<<"POINT_DATA "<<nodes->size()<<std::endl;

        out<<"SCALARS u float 1"<<std::endl;
        out<<"LOOKUP_TABLE default"<<std::endl;
        for (ExporterNodeContainer::iterator node=nodes->begin();
             node!=nodes->end();
             node++)
            out<<(node->vel[dirXP]/node->sigmaWeight)<<std::endl;

        out<<"SCALARS v double 1"<<std::endl;
        out<<"LOOKUP_TABLE default"<<std::endl;
        for (ExporterNodeContainer::iterator node=nodes->begin();
             node!=nodes->end();
             node++)
            out<<(node->vel[dirYP]/node->sigmaWeight)<<std::endl;

        out<<"SCALARS w double 1"<<std::endl;
        out<<"LOOKUP_TABLE default"<<std::endl;
        for (ExporterNodeContainer::iterator node=nodes->begin();
             node!=nodes->end();
             node++)
            out<<(node->vel[dirZP]/node->sigmaWeight)<<std::endl;

        out<<"SCALARS p double 1"<<std::endl;
        out<<"LOOKUP_TABLE default"<<std::endl;
        for (ExporterNodeContainer::iterator node=nodes->begin();
             node!=nodes->end();
             node++)
            out<<(node->p/node->sigmaWeight)<<std::endl;

        out<<"VECTORS velvec double"<<std::endl;
        for (ExporterNodeContainer::iterator node=nodes->begin();
             node!=nodes->end();
             node++)
            out<<(node->vel[dirXP]/node->sigmaWeight)<<" "
               <<(node->vel[dirYP]/node->sigmaWeight)<<" "
               <<(node->vel[dirZP]/node->sigmaWeight)<<std::endl;

        //this BLOCK must be deleted
//        out<<"CELL_DATA "<<dummyCellVals->size()<<std::endl;
//        out<<"SCALARS mmm double 1"<<std::endl;
//        out<<"LOOKUP_TABLE default"<<std::endl;
//        for (int i=0;i<dummyCellVals->size();i++) out<<(*dummyCellVals)[i]<<std::endl;
    }
    //-------------------------------------------------------
    void VTKExport::run(CellContainer *aMesh, std::string fileName/*,DoubleDatumVector* dummyCellVals*/)
    {
        VTKExport *vtkData = new VTKExport(aMesh->size());

        std::ofstream vtkFile;
        vtkFile.open(fileName.c_str(), ios::trunc);
        vtkData->generatePrimaryConnectivities(aMesh);
        vtkData->generateSecondaryConnectivities(aMesh);
        vtkData->writeToStream(vtkFile/*,dummyCellVals*/);
        vtkFile.close();
        delete vtkData;
    }
}

#endif
